/*
 * 农历转换程序 - 适配ESP8266和DS1302 RTC
 * 支持公历转农历，包括闰月计算和干支纪年
 * 使用Rtc_by_Makuna库与DS1302实时时钟通信
 */

#include <Arduino.h>
#include <RtcDS1302.h> // DS1302 RTC库

// 使用ThreeWire通信协议连接DS1302
// 参数说明：IO引脚(数据), SCLK引脚(时钟), CE引脚(片选)
// 请根据实际硬件连接调整引脚号
ThreeWire myWire(4, 5, 2); // IO, SCLK, CE
RtcDS1302<ThreeWire> Rtc(myWire);

/*
 * 农历数据表 (2000-2099年)
 * 每个年份使用3个字节存储农历信息：
 * 字节1: 高4位表示闰月月份(0表示无闰月)，低4位表示农历每月天数信息(从高位到低位依次为1-4月)
 * 字节2: 农历每月天数信息(从高位到低位依次为5-12月)
 * 字节3: 第7位表示闰月天数/第5-6位表示春节阳历月份，低5位（0-4）表示春节阳历日期
 * 每月天数: 1表示大月(30天)，0表示小月(29天)
 */
const uint8_t year_code[] = {
    0x0C, 0x96, 0x45, // 2000
    0x4d, 0x4A, 0xB8, // 2001
    0x0d, 0x4A, 0x4C, // 2002
    0x0d, 0xA5, 0x41, // 2003
    0x25, 0xAA, 0xB6, // 2004
    0x05, 0x6A, 0x49, // 2005

    0x7A, 0xAd, 0xBd, // 2006
    0x02, 0x5d, 0x52, // 2007
    0x09, 0x2d, 0x47, // 2008
    0x5C, 0x95, 0xBA, // 2009
    0x0A, 0x95, 0x4e, // 2010
    0x0B, 0x4A, 0x43, // 2011
    0x4B, 0x55, 0x37, // 2012
    0x0A, 0xd5, 0x4A, // 2013
    0x95, 0x5A, 0xBf, // 2014
    0x04, 0xBA, 0x53, // 2015
    0x0A, 0x5B, 0x48, // 2016
    0x65, 0x2B, 0xBC, // 2017
    0x05, 0x2B, 0x50, // 2018
    0x0A, 0x93, 0x45, // 2019
    0x47, 0x4A, 0xB9, // 2020

    0x06, 0xAA, 0x4C, // 2021
    0x0A, 0xd5, 0x41, // 2022
    0x24, 0xdA, 0xB6, // 2023
    0x04, 0xB6, 0x4A, // 2024
    0x6A, 0x6E, 0x3D, // 2025
    0x0A, 0x4e, 0x51, // 2026
    0x0d, 0x26, 0x46, // 2027
    0x5e, 0x93, 0x3A, // 2028
    0x0d, 0x53, 0x4d, // 2029
    0x05, 0xAA, 0x43, // 2030
    0x36, 0xB5, 0x37, // 2031
    0x09, 0x6d, 0x4B, // 2032
    0xB4, 0xAe, 0xBf, // 2033
    0x04, 0xAd, 0x53, // 2034
    0x0A, 0x4d, 0x48, // 2035

    0x6d, 0x25, 0xBC, // 2036
    0x0d, 0x25, 0x4f, // 2037
    0x0d, 0x52, 0x44, // 2038
    0x5d, 0xAA, 0x38, // 2039
    0x0B, 0x5A, 0x4C, // 2040
    0x05, 0x6d, 0x41, // 2041
    0x24, 0xAd, 0xB6, // 2042
    0x04, 0x9B, 0x4A, // 2043
    0x7A, 0x4B, 0xBe, // 2044
    0x0A, 0x4B, 0x51, // 2045
    0x0A, 0xA5, 0x46, // 2046
    0x5B, 0x52, 0xBA, // 2047
    0x06, 0xd2, 0x4e, // 2048
    0x0A, 0xdA, 0x42, // 2049
    0x35, 0x5B, 0x37, // 2050

    0x09, 0x37, 0x4B, // 2051
    0x84, 0x97, 0xC1, // 2052
    0x04, 0x97, 0x53, // 2053
    0x06, 0x4B, 0x48, // 2054
    0x66, 0xA5, 0x3C, // 2055
    0x0e, 0xA5, 0x4f, // 2056
    0x06, 0xAA, 0x44, // 2057
    0x4A, 0xB6, 0x38, // 2058
    0x0A, 0xAe, 0x4C, // 2059
    0x09, 0x2e, 0x42, // 2060
    0x3C, 0x97, 0x35, // 2061
    0x0C, 0x96, 0x49, // 2062
    0x7d, 0x4A, 0xBd, // 2063
    0x0d, 0x4A, 0x51, // 2064
    0x0d, 0xA5, 0x45, // 2065

    0x55, 0xAA, 0xBA, // 2066
    0x05, 0x6A, 0x4e, // 2067
    0x0A, 0x6d, 0x43, // 2068
    0x45, 0x2e, 0xB7, // 2069
    0x05, 0x2d, 0x4B, // 2070
    0x8A, 0x95, 0xBf, // 2071
    0x0A, 0x95, 0x53, // 2072
    0x0B, 0x4A, 0x47, // 2073
    0x6B, 0x55, 0x3B, // 2074
    0x0A, 0xd5, 0x4f, // 2075
    0x05, 0x5A, 0x45, // 2076
    0x4A, 0x5d, 0x38, // 2077
    0x0A, 0x5B, 0x4C, // 2078
    0x05, 0x2B, 0x42, // 2079
    0x3A, 0x93, 0xB6, // 2080

    0x06, 0x93, 0x49, // 2081
    0x77, 0x29, 0xBd, // 2082
    0x06, 0xAA, 0x51, // 2083
    0x0A, 0xd5, 0x46, // 2084
    0x54, 0xdA, 0xBA, // 2085
    0x04, 0xB6, 0x4e, // 2086
    0x0A, 0x57, 0x43, // 2087
    0x45, 0x27, 0x38, // 2088
    0x0d, 0x16, 0x4A, // 2089
    0x8e, 0x93, 0x3e, // 2090
    0x0d, 0x52, 0x52, // 2091
    0x0d, 0xAA, 0x47, // 2092
    0x66, 0xB5, 0x3B, // 2093
    0x05, 0x6d, 0x4f, // 2094
    0x04, 0xAe, 0x45, // 2095

    0x4A, 0x4e, 0xB9, // 2096
    0x0A, 0x2d, 0x4C, // 2097
    0x0d, 0x15, 0x41, // 2098
    0x2d, 0x92, 0xB5   // 2099
}; 

// 月份天数表 - 用于计算公历日期距离元旦的天数
// 1-9月每月累计天数 (非闰年)
const uint8_t day_code1[9] = {0x00, 0x1f, 0x3b, 0x5a, 0x78, 0x97, 0xb5, 0xd4, 0xf3};
// 10-12月每月累计天数 (非闰年)
const uint16_t day_code2[3] = {0x111, 0x130, 0x14e};

// 全局变量
uint8_t Calendar_Convert_Write_Buffer[2] = {0, 0}; // 农历月日输出缓冲区，BCD格式
bool isLeapMonth = false;                          // 是否为闰月标志
uint16_t lunarYear = 0;                            // 农历年份（公历年）

// 天干地支表
const char* heavenlyStems[10] = {"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"};
const char* earthlyBranches[12] = {"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"};

/**
 * 获取农历月份天数
 * 参数:
 *   month_p - 农历月份(1-13，13表示闰月)
 *   table_addr - 年份在数据表中的起始地址
 * 返回值:
 *   true - 大月(30天)
 *   false - 小月(29天)
 */
bool get_moon_day(uint8_t month_p, uint16_t table_addr)
{
  // 月份位掩码表 - 存储每个月份对应的位掩码
  static const uint8_t month_mask[13] = {
      0x08, 0x04, 0x02, 0x01, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x80 //
  };

  // 月份字节偏移表 - 存储每个月份对应的字节偏移量
  static const uint8_t month_offset[] = {
      0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2 //
  };

  // 检查月份是否有效
  if (month_p < 1 || month_p > 13)
  {
    return false;
  }

  // 获取月份对应的字节偏移量和位掩码
  uint8_t offset = month_offset[month_p - 1];
  uint8_t mask = month_mask[month_p - 1];

  // 读取对应字节并进行位运算
  uint8_t byte_value = year_code[table_addr + offset];
  return (byte_value & mask) != 0;
}

/*
 * 公历转农历函数
 * 参数:
 *   dt - RTC日期时间对象
 * 返回值:
 *   true - 转换成功
 *   false - 转换失败(年份超出范围)
 */
bool solarToLunar(const RtcDateTime &dt)
{
  // 从RtcDateTime获取年月日
  uint16_t year = dt.Year();
  uint8_t month = dt.Month();
  uint8_t day = dt.Day();

  // 只支持2000-2099年
  if (year < 2000 || year > 2099)
  {
    return false;
  }

  // 计算数据表地址 (2000年对应索引0，每年占用3个字节)
  uint16_t table_addr = (year - 2000) * 3;

  // 获取春节信息
  uint8_t spring_info = year_code[table_addr + 2];
  uint8_t spring_month = (spring_info & 0x60) >> 5; // 春节月份(高2位)
  uint8_t spring_day = spring_info & 0x1F;          // 春节日期(低5位)

  // 计算当前日期距离元旦的天数
  uint16_t day_count;
  if (month < 10)
  {
    day_count = day_code1[month - 1] + day - 1; // 1-9月使用day_code1
  }
  else
  {
    day_count = day_code2[month - 10] + day - 1; // 10-12月使用day_code2
  }

  // 闰年二月增加一天(公历闰年规则：能被4整除但不能被100整除，或者能被400整除)
  if ((month > 2) && (year % 4 == 0))
  {
    day_count++;
  }

  // 计算春节距离元旦的天数
  uint16_t spring_day_count;
  if (spring_month == 1)
  {
    spring_day_count = spring_day - 1; // 春节在1月
  }
  else
  {
    spring_day_count = spring_day + 31 - 1; // 春节在2月(1月31天 + 春节日期)
  }

  // 农历转换逻辑
  uint8_t lunar_month, lunar_day;
  isLeapMonth = false; // 重置闰月标志

  if (day_count >= spring_day_count)
  {
    // 春节之后 - 属于当前农历年
    day_count -= spring_day_count; // 计算距离春节的天数
    lunar_month = 1;               // 从正月开始
    lunarYear = year;              // 农历年等于当前公历年

    bool foundLeapMonth = false;                     // 是否已处理闰月
    uint8_t month_index = 1;                         // 当前处理的月份索引
    uint8_t leap_month = year_code[table_addr] >> 4; // 获取闰月信息(高4位)

    // 遍历月份，计算农历月和日
    while (day_count > 0)
    {
      // 检查是否是闰月
      if (month_index == leap_month && !foundLeapMonth)
      {
        foundLeapMonth = true; // 标记已处理闰月

        // 获取闰月天数
        uint8_t days_in_leap_month = get_moon_day(13, table_addr) ? 30 : 29;

        if (day_count < days_in_leap_month)
        {
          // 当前日期在闰月内
          isLeapMonth = true; // 设置闰月标志
          break;              // 退出循环
        }
        else
        {
          day_count -= days_in_leap_month; // 减去闰月天数
        }
      }
      else
      {
        // 普通月份处理
        uint8_t days_in_month = get_moon_day(month_index, table_addr) ? 30 : 29;

        if (day_count < days_in_month)
        {
          break; // 当前日期在本月内，退出循环
        }
        else
        {
          day_count -= days_in_month; // 减去本月天数
          lunar_month++;              // 月份增加
        }
        month_index++; // 处理下一个月份
      }
    }
    lunar_day = day_count + 1; // 计算农历日(day_count是从0开始的)
  }
  else
  {
    // 春节之前，属于上一年
    // 简化处理：直接设置为农历12月
    lunar_month = 12;
    lunar_day = spring_day_count - day_count; // 计算距离正月初一的天数
    isLeapMonth = false;                      // 春节前不会有闰月
    lunarYear = year - 1;                     // 农历年等于上一年
  }

  // 转换为BCD格式并存储结果
  // 月份: 高4位为十位数，低4位为个位数
  Calendar_Convert_Write_Buffer[0] = ((lunar_month / 10) << 4) | (lunar_month % 10);
  // 日期: 高4位为十位数，低4位为个位数
  Calendar_Convert_Write_Buffer[1] = ((lunar_day / 10) << 4) | (lunar_day % 10);

  return true;
}

/*
 * 获取农历日期
 * 参数:
 *   month - 输出参数，农历月份(BCD格式)
 *   day - 输出参数，农历日期(BCD格式)
 *   isLeap - 输出参数，是否为闰月(可选)
 * 返回值:
 *   true - 获取成功
 */
bool getLunarDate(uint8_t *month, uint8_t *day, bool *isLeap = nullptr)
{
  if (month)
    *month = Calendar_Convert_Write_Buffer[0]; // 输出农历月份
  if (day)
    *day = Calendar_Convert_Write_Buffer[1]; // 输出农历日期
  if (isLeap)
    *isLeap = isLeapMonth; // 输出是否为闰月
  return true;
}

/*
 * 获取农历年份（干支纪年）
 * 返回值:
 *   干支纪年字符串
 */
const char* getLunarYearString()
{
  // 计算天干地支索引（2000年是庚辰年）
  int stemIndex = (lunarYear - 2000 + 7) % 10; // 天干索引
  int branchIndex = (lunarYear - 2000 + 5) % 12; // 地支索引
  
  // 处理负数情况
  if (stemIndex < 0) stemIndex += 10;
  if (branchIndex < 0) branchIndex += 12;
  
  // 组合干支字符串
  static char yearStr[10]; // 足够存储"庚子"等
  snprintf(yearStr, sizeof(yearStr), "%s%s", heavenlyStems[stemIndex], earthlyBranches[branchIndex]);
  return yearStr;
}

/*
 * 从RTC获取时间并更新农历日期
 * 返回值:
 *   true - 更新成功
 *   false - 更新失败(RTC时间无效)
 */
bool updateLunarDate()
{
  RtcDateTime now = Rtc.GetDateTime(); // 获取当前RTC时间
  if (!now.IsValid())
  {
    return false; // 时间无效
  }

  return solarToLunar(now); // 转换公历到农历
}

/*
 * 初始化函数
 * 设置串口通信，初始化RTC，检查RTC状态
 */
void setup()
{
  Serial.begin(115200); // 初始化串口通信，波特率115200
  delay(1000);          // 等待串口稳定

  // 初始化RTC
  Rtc.Begin();

  // 检查RTC时间是否有效，如果无效则设置默认时间
  if (!Rtc.IsDateTimeValid())
  {
    Serial.println("RTC lost confidence time, setting default time");
    Rtc.SetDateTime(RtcDateTime(__DATE__, __TIME__)); // 设置编译时间为默认时间
  }

  // 禁止写保护，允许修改RTC时间
  Rtc.SetIsWriteProtected(false);

  // 确保RTC正在运行
  if (!Rtc.GetIsRunning())
  {
    Serial.println("RTC is not running, starting now");
    Rtc.SetIsRunning(true); // 启动RTC
  }

  Serial.println("农历转换程序已启动");
  Serial.println("-------------------");
}

/*
 * 主循环函数
 * 定期更新并显示农历日期
 */
void loop()
{
  // 更新农历日期
  if (updateLunarDate())
  {
    uint8_t lunarMonth, lunarDay;
    bool isLeap;
    getLunarDate(&lunarMonth, &lunarDay, &isLeap);

    // 获取当前RTC时间
    RtcDateTime now = Rtc.GetDateTime();

    // 输出公历日期
    Serial.printf("阳历：%04d-%02d-%02d  时间：%02d:%02d:%02d\n",
                  now.Year(), now.Month(), now.Day(),
                  now.Hour(), now.Minute(), now.Second());
    
    // 输出农历日期（包含干支纪年）
    Serial.printf("农历: %s年 ", getLunarYearString());
    if (isLeap)
      Serial.print("闰"); // 如果是闰月，显示"闰"字
    Serial.printf("%02d月 %02d日\n", lunarMonth, lunarDay);
    Serial.println("-------------------");
  }
  else
  {
    Serial.println("获取农历日期失败，请检查RTC连接");
  }

  delay(5000); // 每5秒更新一次
}